package nautilus.utility;

public class ByteQueue{
	private byte[] queue;
	private int size;
	private int capacity;
	
	/* This is the next position that new coming byte will be allocated */
	private int head; 
	
	/* This is the next position of byte that will be taken from the queue */
	private int tail;
	
	public ByteQueue(int cap){
		capacity = (cap < 1)? 1: cap;
		queue = new byte[capacity];
		size = 0;
		head = tail = 0;
	}
	
	public int getCapacity(){
		return capacity;
	}
	
	public synchronized int getSize(){
		return size;
	}
	
	public synchronized boolean isEmpty(){
		return (size<=0)?true:false;
	}
	
	public synchronized boolean isFull(){
		return size>=capacity?true:false;
	}
	
	public synchronized void waitWhileFull() throws InterruptedException {
		while(isFull()){
			wait();
		}
	}
	
	public synchronized void waitWhileEmpty() throws InterruptedException{
		while(isEmpty()){
			wait();
		}
	}
	
	public synchronized void add(byte b) throws InterruptedException{
		waitWhileFull();
		
		queue[head] = b;
		head = (head + 1) % capacity;
		size++;
		
		//let any waiting threads know about change
		notifyAll();
	}
	
	public synchronized void add(byte[] lst) throws InterruptedException{
		int ptr = 0;
		while(ptr < lst.length){
			waitWhileFull();
			int space = capacity - size;
			int d2End = capacity - head;
			int blockLen = Math.min(space, d2End);
			int byteRemaining = lst.length - ptr;
			int cpyLen = Math.min(blockLen, byteRemaining);
			
			System.arraycopy(lst, ptr, queue, head, cpyLen);
			head = (head + cpyLen)%capacity;
			size += cpyLen;
			ptr += cpyLen;
			notifyAll();
		}
	}
	
	public synchronized void add(byte[] lst, int len) throws InterruptedException{
	
		if(len > lst.length)
			len = lst.length;
			
		int ptr = 0;
		while(ptr < len){
			waitWhileFull();
			int space = capacity - size;
			int d2End = capacity - head;
			int blockLen = Math.min(space, d2End);
			int byteRemaining = len - ptr;
			int cpyLen = Math.min(blockLen, byteRemaining);
			
			System.arraycopy(lst, ptr, queue, head, cpyLen);
			head = (head + cpyLen)%capacity;
			size += cpyLen;
			ptr += cpyLen;
			notifyAll();
		}
	}
	
	public synchronized byte remove() throws InterruptedException{
		waitWhileEmpty();
		
		byte b = queue[tail];
		tail = (tail + 1) % capacity;
		size--;
		
		notifyAll();
		return b;
	}
	
	public synchronized byte[] removeAll()throws InterruptedException{
		if(isEmpty()){
			return new byte[0];
		}
		
		//base on the current size
		byte[] lst = new byte[size];
		
		//copy in the block from tail to end
		int dist2End = capacity - tail;
		int cpyLen = Math.min(size, dist2End);
		System.arraycopy(queue, tail, lst, 0, cpyLen);
		
		//if data wraps around
		if(size > cpyLen)
			System.arraycopy(queue, 0, lst, cpyLen, size - cpyLen);
		
		tail = (tail + size) % capacity;
		size = 0;
		
		notify();
		
		return lst;
	}
}